package com.work.flow.service.impl;

import com.work.flow.common.FileTool;
import com.work.flow.common.FileType;
import com.work.flow.common.NotSameFileException;
import com.work.flow.common.UUIDTool;
import com.work.flow.dao.FileDao;
import com.work.flow.dao.MultipartFileParam;
import com.work.flow.model.PageModel;
import com.work.flow.model.ShapeFile;
import com.work.flow.service.FileService;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.List;
import java.util.UUID;

import static java.lang.Thread.sleep;

@Service
public class FileServiceImpl implements FileService {

    @Autowired
    FileDao fileDao;

    @Value("${oss.path}")
    String path;

    @Override
    public String chunkUploadByMappedByteBuffer(MultipartFileParam param, String filePath) throws IOException,NotSameFileException {

        if(param.getTaskId() == null || "".equals(param.getTaskId())){
            param.setTaskId(UUID.randomUUID().toString());
        }
        /**
         *
         * 1：创建临时文件，和源文件一个路径
         * 2：如果文件路径不存在重新创建
         */
        String fileName = param.getFile().getOriginalFilename();
        String tempFileName = param.getTaskId() + fileName.substring(fileName.lastIndexOf(".")) + "_tmp";
        File fileDir = new File(filePath);
        if(!fileDir.exists()){
            fileDir.mkdirs();
        }
        File tempFile = new File(filePath,tempFileName);
        //第一步
        RandomAccessFile raf = new RandomAccessFile(tempFile,"rw");
        //第二步
        FileChannel fileChannel = raf.getChannel();
        //第三步 计算偏移量
        long position = (param.getChunkNumber()-1) * param.getChunkSize();
        //第四步
        byte[] fileData = param.getFile().getBytes();
        //第五步
        long end=position+fileData.length-1;
        fileChannel.position(position);
        fileChannel.write(ByteBuffer.wrap(fileData));

        fileChannel.force(true);
        fileChannel.close();
        raf.close();
        //第八步
        boolean isComplete = checkUploadStatus(param,fileName,filePath);
        if(isComplete){
            //重命名文件，然后校验MD5文件是否一致
            FileInputStream fileInputStream=new FileInputStream(tempFile.getPath());
            String md5= DigestUtils.md5Hex(fileInputStream);
            fileInputStream.close();

            if(StringUtils.isNotBlank(md5) && !md5.equals(param.getIdentifier())){
                //不是同一文件抛出异常
                throw new NotSameFileException();
            }
            com.work.flow.model.File file=new com.work.flow.model.File();
            file.setUuid(UUIDTool.get32UUID());
            String path=renameFile(filePath+File.separator+tempFileName,fileName);
            if(path.endsWith(FileType.ZIP.getSuffix())){
              ShapeFile shapeFile=unZipShp(file.getUuid(),path);
              path=shapeFile.getPath();
              fileName=shapeFile.getName();
            }else {
              path=copyFileToDir(path,file.getUuid());
            }

            file.setName(fileName);
            file.setPath(path);
            fileDao.insertSelective(file);
        }
        return param.getTaskId();
    }

    private String copyFileToDir(String filePath, String uuid) {
        String dirPath=path+File.separator+uuid;
        return FileTool.copyFileToDir(filePath,dirPath);
    }

    @Override
    public ShapeFile unZipShp(String id,String zipPath) {
        ShapeFile shapeFile=new ShapeFile();
        shapeFile.setDir(path+File.separator+id);

        FileTool.unzip(zipPath,shapeFile.getDir());
        List<String> files=FileTool.findFile(shapeFile.getDir(), FileType.SHP.getSuffix());
        if(files.size()>0){
            shapeFile.setName(FileTool.getFileName(files.get(0)));
        }else {
            System.out.println("未找到文件");
        }
        shapeFile.setPath(shapeFile.getDir()+File.separator+shapeFile.getName());
        FileTool.deleteFile(zipPath);
        return shapeFile;
    }

    @Override
    public com.work.flow.model.File getFileById(String id) {
        return fileDao.selectByPrimaryKey(id);
    }

    /**
     * 获取总数
     *
     * @return
     */
    @Override
    public Long getCount(String key) {
        return fileDao.getCount(key);
    }

    /**
     * 获取文件列表
     *
     * @return
     */
    @Override
    public List<com.work.flow.model.File> getList() {
        return fileDao.getList();
    }

    /**
     * 分页查询
     *
     * @param pageModel
     * @return
     */
    @Override
    public List<com.work.flow.model.File> queryByPage(PageModel pageModel) {
        return fileDao.queryByPage(pageModel);
    }

    /**
     * 删除文件
     *
     * @param id
     */
    @Override
    public void deleteById(String id) {
        com.work.flow.model.File file=fileDao.selectByPrimaryKey(id);
        String parentPath=new File(file.getPath()).getParentFile().getAbsolutePath();
        FileTool.deleteFiles(parentPath);
        fileDao.deleteById(id);
    }

    /**
     * 下载文件
     *
     * @param path
     * @param response
     */
    @Override
    public void downLoadFileByPath(String path, HttpServletResponse response) throws Exception {
        File file = new File(path);

        if (!file.exists()||file.isDirectory()) {
            throw new Exception("文件不存在");
        }
        String fileName = FileTool.getFileName(path);
        if(fileName.endsWith(FileType.SHP.getSuffix())){
            System.out.println("是shp文件");
            String tempPath = FileTool.fileToZip(file.getParentFile().getAbsolutePath());
            sleep(5000);
            fileName = FileTool.getFileName(tempPath);
        }

        response.reset();
        // 设置ContentType，响应内容为二进制数据流，编码为utf-8，此处设定的编码是文件内容的编码
        response.setContentType("application/octet-stream;charset=utf-8");
        response.setCharacterEncoding("UTF-8");
        response.addHeader("Content-Length", "" + file.length());
        // 以（Content-Disposition: attachment; filename="filename.jpg"）格式设定默认文件名，设定utf编码，此处的编码是文件名的编码，使能正确显示中文文件名
        response.setHeader("Content-Disposition", "attachment;fileName=" + fileName + ";filename*=utf-8''" + URLEncoder.encode(fileName, "utf-8"));

        // 实现文件下载
        byte[] buffer = new byte[1024];
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        try {
            fis = new FileInputStream(file);
            bis = new BufferedInputStream(fis);
            // 获取字节流
            OutputStream os = response.getOutputStream();
            int i = bis.read(buffer);
            while (i != -1) {
                os.write(buffer, 0, i);
                i = bis.read(buffer);
            }
            os.flush();
            System.out.println("Download successfully!");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Download failed!");
        } finally {

            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 文件重命名
     * @param toFileNewName 新的名字
     * @return
     */
    public String renameFile(String toBeRenamedFile, String toFileNewName) {
        File toBeRenamed=new File(toBeRenamedFile);
        //检查要重命名的文件是否存在，是否是文件
        if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
            System.out.println("文件不存在");
            return null;
        }
        String p = toBeRenamed.getParent();
        File newFile = new File(p + File.separatorChar + toFileNewName);
        //修改文件名
        if (toBeRenamed.renameTo(newFile)) {
            System.out.println("renamed");
        } else {
            try{
                FileUtils.copyFile(toBeRenamed, newFile); // 将旧文件拷贝给新文件
                System.out.println(toBeRenamed +" -> "+ newFile);
                if(toBeRenamed.delete()){
                    System.out.println("删除成功");
                }else {
                    System.out.println("删除失败");
                }
            } catch (IOException e) {
                System.out.println("Failed to rename ["+toBeRenamed+"] to ["+newFile+"].");
            }

        }
        return newFile.getAbsolutePath();
    }
    /** 清空文件内容 */
    public static void emptyFileContent(File file) {
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(file);
            out.write(new byte[0]);
            out.close();
        } catch (Exception e) {
            System.out.println("Can't not empty " + file.getName());
        }
    }
    /**
     * 检查文件上传进度
     * @return
     */
    public boolean checkUploadStatus(MultipartFileParam param,String fileName,String filePath) throws IOException {
        File confFile = new File(filePath,fileName+".conf");
        RandomAccessFile confAccessFile = new RandomAccessFile(confFile,"rw");
        //设置文件长度
        confAccessFile.setLength(param.getTotalChunks());
        //设置起始偏移量
        confAccessFile.seek(param.getChunkNumber()-1);
        //将指定的一个字节写入文件中 127，
        confAccessFile.write(Byte.MAX_VALUE);
        byte[] completeStatusList = FileUtils.readFileToByteArray(confFile);
        confAccessFile.close();//不关闭会造成无法占用
        //这一段逻辑有点复杂，看的时候思考了好久，创建conf文件文件长度为总分片数，每上传一个分块即向conf文件中写入一个127，那么没上传的位置就是默认的0,已上传的就是Byte.MAX_VALUE 127
        for(int i = 0; i<completeStatusList.length; i++){
            if(completeStatusList[i]!=Byte.MAX_VALUE){
                return false;
            }
        }
        //如果全部文件上传完成，删除conf文件
        confFile.delete();
        return true;
    }

    /**
     * 在MappedByteBuffer释放后再对它进行读操作的话就会引发jvm crash，在并发情况下很容易发生
     * 正在释放时另一个线程正开始读取，于是crash就发生了。所以为了系统稳定性释放前一般需要检 查是否还有线程在读或写
     * @param mappedByteBuffer
     */
    public static void freedMappedByteBuffer(final MappedByteBuffer mappedByteBuffer) {
        try {
            if (mappedByteBuffer == null) {
                return;
            }
            mappedByteBuffer.force();
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    try {
                        Method getCleanerMethod = mappedByteBuffer.getClass().getMethod("cleaner", new Class[0]);
                        //可以访问private的权限
                        getCleanerMethod.setAccessible(true);
                        //在具有指定参数的 方法对象上调用此 方法对象表示的底层方法
                        sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(mappedByteBuffer,
                                new Object[0]);
                        cleaner.clean();
                    } catch (Exception e) {
                        e.printStackTrace();
                        System.out.println("清理缓存出错!!!"+e.getMessage());
                    }
                    System.out.println("缓存清理完毕!!!");
                    return null;
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
